#include "NetUtil.hpp"
#include "ExceptionUtil.hpp"
#include "ErrnoException.hpp"

#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>
#include <fcntl.h>


namespace kieper {
    namespace netutil {
        int createUdpSock() {
            int sock;
            if ((sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
                THROW(ErrnoException, "Failed to create UDP socket");
            }
            return sock;
        }

        void bindSock(int sock, int port) {
            sockaddr_in server;
            memset(&server, 0, sizeof(server));
            server.sin_family = AF_INET;
            server.sin_addr.s_addr = htonl(INADDR_ANY);
            server.sin_port = htons(port);

            if (bind(sock, (sockaddr *) &server, sizeof(server)) < 0) {
                THROW(ErrnoException, "Failed to bind to UDP port");
            }
        }

        void setNonBlocking(int sock) {
            int desc = fcntl(sock, F_GETFL, 0);
            if (desc < 0) {
                THROW(ErrnoException, "Failed to retrieve fcntl descriptor");
            }

            if (fcntl(sock, F_SETFL, desc | O_NONBLOCK) < 0) {
                THROW(ErrnoException, "Failed to set socket as nonblocking");
            }
        }

        void setBlocking(int sock) {
            int desc = fcntl(sock, F_GETFL, 0);
            if (desc < 0) {
                THROW(ErrnoException, "Failed to retrieve fcntl descriptor");
            }

            if (fcntl(sock, F_SETFL, desc & ~ O_NONBLOCK) < 0) {
                THROW(ErrnoException, "Failed to set socket as blocking");
            }
        }
        
        void setBroadcast(int sock) {
            int enableBroadcast = 1;
            if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &enableBroadcast,
                sizeof(enableBroadcast)) < 0) {
                THROW(ErrnoException, "Failed to set socket as broadcast");
            }
        }
        

        void setAsNbo16(char* buffer, unsigned int index, uint16_t val) {
            uint16_t* bufferAsUint16 = reinterpret_cast<uint16_t*> (buffer);
            bufferAsUint16[index] = htons(val);
        }

        void setAsNbo32(char* buffer, unsigned int index, uint32_t val) {
            uint32_t* bufferAsUint32 = reinterpret_cast<uint32_t*> (buffer);
            bufferAsUint32[index] = htonl(val);
        }
        
        uint16_t getFromNbo16(const char* buffer, unsigned int index) {
            const uint16_t* bufferAsUint16 = reinterpret_cast<const uint16_t*> (buffer);
            return ntohs(bufferAsUint16[index]);
        }

        uint32_t getFromNbo32(const char* buffer, unsigned int index) {
            const uint32_t* bufferAsUint32 = reinterpret_cast<const uint32_t*> (buffer);
            return ntohl(bufferAsUint32[index]);
        }
    }
}
